home *** CD-ROM | disk | FTP | other *** search
/ Tech Arsenal 1 / Tech Arsenal (Arsenal Computer).ISO / tek-04 / bipl.zip / PROCS.ZIP / FINDRE.ICN < prev    next >
Text File  |  1993-01-27  |  21KB  |  709 lines

  1. ########################################################################
  2. #    
  3. #    File:     findre.icn
  4. #    
  5. #    Subject:  Procedures to find regular expression
  6. #    
  7. #    Author:   Richard L. Goerwitz
  8. #
  9. #    Date:     June 1, 1991
  10. #
  11. #######################################################################
  12. #
  13. #    Version:  1.14
  14. #
  15. ###########################################################################
  16. #
  17. #    findre() is like the Icon builtin function find(),
  18. #  except that it takes, as its first argument, a regular expression
  19. #  pretty much like the ones the UNIX egrep command uses (the few
  20. #  minor differences are listed below).  Its syntax is the same as
  21. #  find's (i.e. findre(s1,s2,i,j)), with the exception that a no-
  22. #  argument invocation wipes out all static structures utilized by
  23. #  findre, and then forces a garbage collection.
  24. #
  25. #  (For those not familiar with regular expressions and the UNIX egrep
  26. #  command: findre() offers a simple and compact wildcard-based search
  27. #  system.  If you do a lot of searches through text files, or write
  28. #  programs which do searches based on user input, then findre is a
  29. #  utility you might want to look over.)
  30. #
  31. #  Important differences between find and findre:  As noted above,
  32. #  findre() is just a find() function that takes a regular expression
  33. #  as its first argument.  One major problem with this setup is that
  34. #  it leaves the user with no easy way to tab past a matched
  35. #  substring, as with
  36. #    s ? write(tab(find("hello")+5))
  37. #
  38. #  In order to remedy this intrinsic deficiency, findre() sets the
  39. #  global variable __endpoint to the first position after any given
  40. #  match occurs.  Use this variable with great care, preferably
  41. #  assigning its value to some other variable immediately after the
  42. #  match (for example, findre("hello [.?!]*",s) & tmp := __endpoint).
  43. #  Otherwise, you will certainly run into trouble.  (See the example
  44. #  below for an illustration of how __endpoint is used).
  45. #
  46. #  Important differences between egrep and findre:  findre utilizes
  47. #  the same basic language as egrep.  The only big difference is that
  48. #  findre uses intrinsic Icon data structures and escaping conven-
  49. #  tions rather than those of any particular UNIX variant.  Be care-
  50. #  ful!  If you put findre("\(hello\)",s) into your source file,
  51. #  findre will treat it just like findre("(hello)",s).  If, however,
  52. #  you enter '\(hello\)' at run-time (via, say, findre(!&input,s)),
  53. #  what Icon receives will depend on your operating system (most
  54. #  likely, a trace will show "\\(hello\\)").
  55. #
  56. #  Bugs:  Space has essentially been conserved at the expense of time
  57. #  in the automata produced by findre().  The algorithm, in other
  58. #  words, will produce the equivalent of a pushdown automaton under
  59. #  certain circumstances, rather than strive (at the expense of space)
  60. #  for full determinism.  I tried to make up a nfa -> dfa converter
  61. #  that would only create that portion of the dfa it needed to accept
  62. #  or reject a string, but the resulting automaton was actually quite
  63. #  slow (if anyone can think of a way to do this in Icon, and keep it
  64. #  small and fast, please let us all know about it).  Note that under
  65. #  version 8 of Icon, findre takes up negligible storage space, due to
  66. #  the much improved hashing algorithm.  I have not tested it under
  67. #  version 7, but I would expect it to use up quite a bit more space
  68. #  in that environment.
  69. #
  70. #  Important note:  findre takes a shortest-possible-match approach
  71. #  to regular expressions.  In other words, if you look for "a*",
  72. #  findre will not even bother looking for an "a."  It will just match
  73. #  the empty string.  Without this feature, findre would perform a bit
  74. #  more slowly.  The problem with such an approach is that often the
  75. #  user will want to tab past the longest possible string of matched
  76. #  characters (say tab((findre("a*|b*"), __endpoint)).  In circumstan-
  77. #  ces like this, please just use something like:
  78. #
  79. #      s ? {
  80. #          tab(find("a")) &  # or use Arb() from the IPL (patterns.icn)
  81. #          tab(many('a'))
  82. #          tab(many('b'))
  83. #      }
  84. #
  85. #  or else use some combination of findre and the above.
  86. #    
  87. ########################################################################
  88. #
  89. #  Regular expression syntax: Regular expression syntax is complex,
  90. #  and yet simple.  It is simple in the sense that most of its power
  91. #  is concentrated in about a dozen easy-to-learn symbols.  It is
  92. #  complex in the sense that, by combining these symbols with
  93. #  characters, you can represent very intricate patterns.
  94. #
  95. #  I make no pretense here of offering a full explanation of regular
  96. #  expressions, their usage, and the deeper nuances of their syntax.
  97. #  As noted above, this should be gleaned from a UNIX manual.  For
  98. #  quick reference, however, I have included a brief summary of all
  99. #  the special symbols used, accompanied by an explanation of what
  100. #  they mean, and, in some cases, of how they are used (most of this
  101. #  is taken from the comments prepended to Jerry Nowlin's Icon-grep
  102. #  command, as posted a couple of years ago):
  103. #
  104. #     ^   -  matches if the following pattern is at the beginning
  105. #            of a line (i.e. ^# matches lines beginning with "#")
  106. #     $   -  matches if the preceding pattern is at the end of a line
  107. #     .   -  matches any single character
  108. #     +   -  matches from 1 to any number of occurrences of the
  109. #            previous expression (i.e. a character, or set of paren-
  110. #            thesized/bracketed characters)
  111. #     *   -  matches from 0 to any number of occurrences of the previous
  112. #            expression
  113. #     \   -  removes the special meaning of any special characters
  114. #            recognized by this program (i.e if you want to match lines
  115. #            beginning with a "[", write ^\[, and not ^[)
  116. #     |   -  matches either the pattern before it, or the one after
  117. #            it (i.e. abc|cde matches either abc or cde)
  118. #     []  -  matches any member of the enclosed character set, or,
  119. #            if ^ is the first character, any nonmember of the
  120. #            enclosed character set (i.e. [^ab] matches any character
  121. #         _except_ a and b).
  122. #     ()  -  used for grouping (e.g. ^(abc|cde)$ matches lines consist-
  123. #            ing of either "abc" or "cde," while ^abc|cde$ matches
  124. #            lines either beginning with "abc" or ending in "cde")
  125. #
  126. #########################################################################
  127. #
  128. #  Example program:
  129. #
  130. #  procedure main(a)
  131. #      while line := !&input do {
  132. #          token_list := tokenize_line(line,a[1])
  133. #          every write(!token_list)
  134. #      }
  135. #  end
  136. #
  137. #  procedure tokenize_line(s,sep)
  138. #      tmp_lst := []
  139. #      s ? {
  140. #          while field := tab(findre(sep)|0) &
  141. #          mark := __endpoint
  142. #          do {
  143. #              put(tmp_lst,"" ~== field)
  144. #              if pos(0) then break
  145. #              else tab(mark)
  146. #          }
  147. #      }
  148. #      return tmp_lst
  149. #  end
  150. #
  151. #  The above program would be compiled with findre (e.g. "icont
  152. #  test_prg.icn findre.icn") to produce a single executable which
  153. #  tokenizes each line of input based on a user-specified delimiter.
  154. #  Note how __endpoint is set soon after findre() succeeds.  Note
  155. #  also how empty fields are excluded with "" ~==, etc.  Finally, note
  156. #  that the temporary list, tmp_lst, is not needed.  It is included
  157. #  here merely to illustrate one way in which tokens might be stored.
  158. #
  159. #  Tokenizing is, of course, only one of many uses one might put
  160. #  findre to.  It is very helpful in allowing the user to construct
  161. #  automata at run-time.  If, say, you want to write a program that
  162. #  searches text files for patterns given by the user, findre would be
  163. #  a perfect utility to use.  Findre in general permits more compact
  164. #  expression of patterns than one can obtain using intrinsic Icon
  165. #  scanning facilities.  Its near complete compatibility with the UNIX
  166. #  regexp library, moreover, makes for greater ease of porting,
  167. #  especially in cases where Icon is being used to prototype C code.
  168. #
  169. #########################################################################
  170.  
  171. global state_table, parends_present, slash_present
  172. global biggest_nonmeta_str, __endpoint
  173. record o_a_s(op,arg,state)
  174.  
  175.  
  176. procedure findre(re, s, i, j)
  177.  
  178.     local p, x, nonmeta_len
  179.     static FSTN_table, STRING_table
  180.     initial {
  181.     FSTN_table := table()
  182.     STRING_table := table()
  183.     }
  184.  
  185.     if /re then {
  186.     FSTN_table := table()
  187.     STRING_table := table()
  188.     collect()  # do it *now*
  189.     return
  190.     }
  191.  
  192.     /s := &subject
  193.     if \i then {
  194.     if i < 1 then
  195.         i := *s + (i+1)
  196.     }
  197.     else i := \&pos | 1
  198.     if \j then {
  199.     if j < 1 then
  200.         j := *s + (j+1)
  201.     }
  202.  
  203.     else j := *s+1
  204.     if /FSTN_table[re] then {
  205.     # If we haven't seen this re before, then...
  206.     if \STRING_table[re] then {
  207.         # ...if it's in the STRING_table, use plain find()
  208.         every p := find(STRING_table[re],s,i,j)
  209.         do { __endpoint := p + *STRING_table[re]; suspend p }
  210.         fail
  211.     }
  212.     else {
  213.         # However, if it's not in the string table, we have to
  214.         # tokenize it and check for metacharacters.  If it has
  215.         # metas, we create an FSTN, and put that into FSTN_table;
  216.         # otherwise, we just put it into the STRING_table.
  217.         tokenized_re := tokenize(re)
  218.         if 0 > !tokenized_re then {
  219.         # if at least one element is < 0, re has metas
  220.         MakeFSTN(tokenized_re) | err_out(re,2)
  221.         # both biggest_nonmeta_str and state_table are global
  222.         /FSTN_table[re] := [.biggest_nonmeta_str, copy(state_table)]
  223.         }
  224.         else {
  225.         # re has no metas; put the input string into STRING_table
  226.         # for future reference, and execute find() at once
  227.         tmp := ""; every tmp ||:= char(!tokenized_re)
  228.         insert(STRING_table,re,tmp)
  229.         every p := find(STRING_table[re],s,i,j)
  230.         do { __endpoint := p + *STRING_table[re]; suspend p }
  231.         fail
  232.         }
  233.     }
  234.     }
  235.  
  236.  
  237.     if nonmeta_len := (1 < *FSTN_table[re][1]) then {
  238.     # If the biggest non-meta string in the original re
  239.     # was more than 1, then put in a check for it...
  240.     s[1:j] ? {
  241.         tab(x := i to j - nonmeta_len) &
  242.         (find(FSTN_table[re][1]) | fail) \ 1 &
  243.         (__endpoint := apply_FSTN(&null,FSTN_table[re][2])) &
  244.         (suspend x)
  245.     }
  246.     }
  247.     else {
  248.     #...otherwise it's not worth worrying about the biggest nonmeta str
  249.     s[1:j] ? {
  250.         tab(x := i to j) &
  251.         (__endpoint := apply_FSTN(&null,FSTN_table[re][2])) &
  252.         (suspend x)
  253.     }
  254.     }
  255.  
  256. end
  257.  
  258.  
  259.  
  260. procedure apply_FSTN(ini,tbl)
  261.  
  262.     static s_tbl
  263.     local POS, tmp, fin
  264.  
  265.     /ini := 1 & s_tbl := tbl & biggest_pos := 1
  266.     if ini = 0 then {
  267.     return &pos
  268.     }
  269.     POS := &pos
  270.     fin := 0
  271.  
  272.     repeat {
  273.     if tmp := !s_tbl[ini] &
  274.         tab(tmp.op(tmp.arg))
  275.     then {
  276.         if tmp.state = fin
  277.         then return &pos
  278.         else ini := tmp.state
  279.     }
  280.     else (&pos := POS, fail)
  281.     }
  282.  
  283. end
  284.     
  285.  
  286.  
  287. procedure tokenize(s)
  288.  
  289.     local chr, tmp
  290.  
  291.     token_list := list()
  292.     s ? {
  293.     tab(many('*+?|'))
  294.     while chr := move(1) do {
  295.         if chr == "\\"
  296.         # it can't be a metacharacter; remove the \ and "put"
  297.         # the integer value of the next chr into token_list
  298.         then put(token_list,ord(move(1))) | err_out(s,2,chr)
  299.         else if any('*+()|?.$^',chr)
  300.         then {
  301.         # Yuck!  Egrep compatibility stuff.
  302.         case chr of {
  303.             "*"    : {
  304.             tab(many('*+?'))
  305.             put(token_list,-ord("*"))
  306.             }
  307.             "+"    : {
  308.             tmp := tab(many('*?+')) | &null
  309.             if upto('*?',\tmp)
  310.             then put(token_list,-ord("*"))
  311.             else put(token_list,-ord("+"))
  312.             }
  313.             "?"    : {
  314.             tmp := tab(many('*?+')) | &null
  315.             if upto('*+',\tmp)
  316.             then put(token_list,-ord("*"))
  317.             else put(token_list,-ord("?"))
  318.             }
  319.             "("    : {
  320.             tab(many('*+?'))
  321.             put(token_list,-ord("("))
  322.             }
  323.             default: {
  324.             put(token_list,-ord(chr))
  325.             }
  326.         }
  327.         }
  328.         else {
  329.         case chr of {
  330.             # More egrep compatibility stuff.
  331.             "["    : {
  332.             b_loc := find("[") | *&subject+1
  333.             every next_one := find("]",,,b_loc)
  334.             \next_one ~= &pos | err_out(s,2,chr)
  335.             put(token_list,-ord(chr))
  336.             }
  337.                     "]"    : {
  338.             if &pos = (\next_one+1)
  339.             then put(token_list,-ord(chr)) &
  340.                  next_one := &null
  341.             else put(token_list,ord(chr))
  342.             }
  343.             default: put(token_list,ord(chr))
  344.         }
  345.         }
  346.     }
  347.     }
  348.  
  349.     token_list := UnMetaBrackets(token_list)
  350.  
  351.     fixed_length_token_list := list(*token_list)
  352.     every i := 1 to *token_list
  353.     do fixed_length_token_list[i] := token_list[i]
  354.     return fixed_length_token_list
  355.  
  356. end
  357.  
  358.  
  359.  
  360. procedure UnMetaBrackets(l)
  361.  
  362.     # Since brackets delineate a cset, it doesn't make
  363.     # any sense to have metacharacters inside of them.
  364.     # UnMetaBrackets makes sure there are no metacharac-
  365.     # ters inside of the braces.
  366.  
  367.     local tmplst, i, Lb, Rb
  368.  
  369.     tmplst := list(); i := 0
  370.     Lb := -ord("[")
  371.     Rb := -ord("]")
  372.  
  373.     while (i +:= 1) <= *l do {
  374.     if l[i] = Lb then {
  375.         put(tmplst,l[i])
  376.         until l[i +:= 1] = Rb
  377.         do put(tmplst,abs(l[i]))
  378.         put(tmplst,l[i])
  379.     }
  380.     else put(tmplst,l[i])
  381.     }
  382.     return tmplst
  383.  
  384. end
  385.  
  386.  
  387.  
  388. procedure MakeFSTN(l,INI,FIN)
  389.  
  390.     # MakeFSTN recursively descends through the tree structure
  391.     # implied by the tokenized string, l, recording in (global)
  392.     # fstn_table a list of operations to be performed, and the
  393.     # initial and final states which apply to them.
  394.  
  395.     # global biggest_nonmeta_str, slash_present, parends_present
  396.     static Lp, Rp, Sl, Lb, Rb, Caret_inside, Dot, Dollar, Caret_outside
  397.     local i, inter, inter2, tmp
  398.     initial {
  399.     Lp := -ord("("); Rp := -ord(")")
  400.     Sl := -ord("|")
  401.     Lb := -ord("["); Rb := -ord("]"); Caret_inside := ord("^")
  402.     Dot := -ord("."); Dollar := -ord("$"); Caret_outside := -ord("^")
  403.     }
  404.  
  405.     /INI := 1 & state_table := table() &
  406.     NextState("new") & biggest_nonmeta_str := ""
  407.     /FIN := 0
  408.  
  409.     # I haven't bothered to test for empty lists everywhere.
  410.     if *l = 0 then {
  411.     /state_table[INI] := []
  412.     put(state_table[INI],o_a_s(zSucceed,&null,FIN))
  413.     return
  414.     }
  415.  
  416.     # HUNT DOWN THE SLASH (ALTERNATION OPERATOR)
  417.     every i := 1 to *l do {
  418.     if l[i] = Sl & tab_bal(l,Lp,Rp) = i then {
  419.         if i = 1 then err_out(l,2,char(abs(l[i]))) else {
  420.         /slash_present := "yes"
  421.         inter := NextState()
  422.         inter2:= NextState()
  423.         MakeFSTN(l[1:i],inter2,FIN)
  424.         MakeFSTN(l[i+1:0],inter,FIN)
  425.         /state_table[INI] := []
  426.         put(state_table[INI],o_a_s(apply_FSTN,inter2,0))
  427.         put(state_table[INI],o_a_s(apply_FSTN,inter,0))
  428.         return
  429.         }
  430.     }
  431.     }
  432.  
  433.     # HUNT DOWN PARENTHESES
  434.     if l[1] = Lp then {
  435.     i := tab_bal(l,Lp,Rp) | err_out(l,2,"(")
  436.     inter := NextState()
  437.     if any('*+?',char(abs(0 > l[i+1]))) then {
  438.         case l[i+1] of {
  439.         -ord("*")   : {
  440.             /state_table[INI] := []
  441.             put(state_table[INI],o_a_s(apply_FSTN,inter,0))
  442.             MakeFSTN(l[2:i],INI,INI)
  443.             MakeFSTN(l[i+2:0],inter,FIN)
  444.             return
  445.         }
  446.         -ord("+")   : {
  447.             inter2 := NextState()
  448.             /state_table[inter2] := []
  449.             MakeFSTN(l[2:i],INI,inter2)
  450.             put(state_table[inter2],o_a_s(apply_FSTN,inter,0))
  451.             MakeFSTN(l[2:i],inter2,inter2)
  452.             MakeFSTN(l[i+2:0],inter,FIN)
  453.             return
  454.         }
  455.         -ord("?")   : {
  456.             /state_table[INI] := []
  457.             put(state_table[INI],o_a_s(apply_FSTN,inter,0))
  458.             MakeFSTN(l[2:i],INI,inter)
  459.             MakeFSTN(l[i+2:0],inter,FIN)
  460.             return
  461.         }
  462.         }
  463.     }
  464.     else {
  465.         MakeFSTN(l[2:i],INI,inter)
  466.         MakeFSTN(l[i+1:0],inter,FIN)
  467.         return
  468.     }
  469.     }
  470.     else {     # I.E. l[1] NOT = Lp (left parenthesis as -ord("("))
  471.     every i := 1 to *l do {
  472.         case l[i] of {
  473.         Lp     : {
  474.             inter := NextState()
  475.             MakeFSTN(l[1:i],INI,inter)
  476.             /parends_present := "yes"
  477.             MakeFSTN(l[i:0],inter,FIN)
  478.             return
  479.         }
  480.         Rp     : err_out(l,2,")")
  481.         }
  482.     }
  483.     }
  484.  
  485.     # NOW, HUNT DOWN BRACKETS
  486.     if l[1] = Lb then {
  487.     i := tab_bal(l,Lb,Rb) | err_out(l,2,"[")
  488.     inter := NextState()
  489.     tmp := ""; every tmp ||:= char(l[2 to i-1])
  490.     if Caret_inside = l[2]
  491.     then tmp := ~cset(Expand(tmp[2:0]))
  492.     else tmp :=  cset(Expand(tmp))
  493.     if any('*+?',char(abs(0 > l[i+1]))) then {
  494.         case l[i+1] of {
  495.         -ord("*")   : {
  496.             /state_table[INI] := []
  497.             put(state_table[INI],o_a_s(apply_FSTN,inter,0))
  498.             put(state_table[INI],o_a_s(any,tmp,INI))
  499.             MakeFSTN(l[i+2:0],inter,FIN)
  500.             return
  501.         }
  502.         -ord("+")   : {
  503.             inter2 := NextState()
  504.             /state_table[INI] := []
  505.             put(state_table[INI],o_a_s(any,tmp,inter2))
  506.             /state_table[inter2] := []
  507.             put(state_table[inter2],o_a_s(apply_FSTN,inter,0))
  508.             put(state_table[inter2],o_a_s(any,tmp,inter2))
  509.             MakeFSTN(l[i+2:0],inter,FIN)
  510.             return
  511.         }
  512.         -ord("?")   : {
  513.             /state_table[INI] := []
  514.             put(state_table[INI],o_a_s(apply_FSTN,inter,0))
  515.             put(state_table[INI],o_a_s(any,tmp,inter))
  516.             MakeFSTN(l[i+2:0],inter,FIN)
  517.             return
  518.         }
  519.         }
  520.     }
  521.     else {
  522.         /state_table[INI] := []
  523.         put(state_table[INI],o_a_s(any,tmp,inter))
  524.         MakeFSTN(l[i+1:0],inter,FIN)
  525.         return
  526.     }
  527.     }
  528.     else {           # I.E. l[1] not = Lb
  529.     every i := 1 to *l do {
  530.         case l[i] of {
  531.         Lb     : {
  532.             inter := NextState()
  533.             MakeFSTN(l[1:i],INI,inter)
  534.             MakeFSTN(l[i:0],inter,FIN)
  535.             return
  536.         }
  537.         Rb     : err_out(l,2,"]")
  538.         }
  539.     }
  540.     }
  541.  
  542.     # FIND INITIAL SEQUENCES OF POSITIVE INTEGERS, CONCATENATE THEM
  543.     if i := match_positive_ints(l) then {
  544.     inter := NextState()
  545.     tmp := Ints2String(l[1:i])
  546.     # if a slash has been encountered already, forget optimizing
  547.         # in this way; if parends are present, too, then forget it,
  548.         # unless we are at the beginning or end of the input string
  549.     if  INI = 1 | FIN = 2 | /parends_present &
  550.         /slash_present & *tmp > *biggest_nonmeta_str
  551.     then biggest_nonmeta_str := tmp
  552.     /state_table[INI] := []
  553.     put(state_table[INI],o_a_s(match,tmp,inter))
  554.     MakeFSTN(l[i:0],inter,FIN)
  555.     return
  556.     }
  557.  
  558.     # OKAY, CLEAN UP ALL THE JUNK THAT'S LEFT
  559.     i := 0
  560.     while (i +:= 1) <= *l do {
  561.     case l[i] of {
  562.         Dot          : { Op := any;   Arg := &cset }
  563.         Dollar       : { Op := pos;   Arg := 0     }
  564.         Caret_outside: { Op := pos;   Arg := 1     }
  565.         default      : { Op := match; Arg := char(0 < l[i]) }
  566.     } | err_out(l,2,char(abs(l[i])))
  567.     inter := NextState()
  568.     if any('*+?',char(abs(0 > l[i+1]))) then {
  569.         case l[i+1] of {
  570.         -ord("*")   : {
  571.             /state_table[INI] := []
  572.             put(state_table[INI],o_a_s(apply_FSTN,inter,0))
  573.             put(state_table[INI],o_a_s(Op,Arg,INI))
  574.             MakeFSTN(l[i+2:0],inter,FIN)
  575.             return
  576.         }
  577.         -ord("+")   : {
  578.             inter2 := NextState()
  579.             /state_table[INI] := []
  580.             put(state_table[INI],o_a_s(Op,Arg,inter2))
  581.             /state_table[inter2] := []
  582.             put(state_table[inter2],o_a_s(apply_FSTN,inter,0))
  583.             put(state_table[inter2],o_a_s(Op,Arg,inter2))
  584.             MakeFSTN(l[i+2:0],inter,FIN)
  585.             return
  586.         }
  587.         -ord("?")   : {
  588.             /state_table[INI] := []
  589.             put(state_table[INI],o_a_s(apply_FSTN,inter,0))
  590.             put(state_table[INI],o_a_s(Op,Arg,inter))
  591.             MakeFSTN(l[i+2:0],inter,FIN)
  592.             return
  593.         }
  594.         }
  595.     }
  596.     else {
  597.         /state_table[INI] := []
  598.         put(state_table[INI],o_a_s(Op,Arg,inter))
  599.         MakeFSTN(l[i+1:0],inter,FIN)
  600.         return
  601.     }
  602.     }
  603.  
  604.     # WE SHOULD NOW BE DONE INSERTING EVERYTHING INTO state_table
  605.     # IF WE GET TO HERE, WE'VE PARSED INCORRECTLY!
  606.     err_out(l,4)
  607.  
  608. end
  609.  
  610.  
  611.  
  612. procedure NextState(new)
  613.     static nextstate
  614.     if \new then nextstate := 1
  615.     else nextstate +:= 1
  616.     return nextstate
  617. end
  618.  
  619.  
  620.  
  621. procedure err_out(x,i,elem)
  622.     writes(&errout,"Error number ",i," parsing ",image(x)," at ")
  623.     if \elem 
  624.     then write(&errout,image(elem),".")
  625.     else write(&errout,"(?).")
  626.     exit(i)
  627. end
  628.  
  629.  
  630.  
  631. procedure zSucceed()
  632.     return .&pos
  633. end
  634.  
  635.  
  636.  
  637. procedure Expand(s)
  638.  
  639.     s2 := ""
  640.     s ? {
  641.     s2 ||:= ="^"
  642.     s2 ||:= ="-"
  643.     while s2 ||:= tab(find("-")-1) do {
  644.         if (c1 := move(1), ="-",
  645.         c2 := move(1),
  646.         c1 << c2)
  647.         then every s2 ||:= char(ord(c1) to ord(c2))
  648.         else s2 ||:= 1(move(2), not(pos(0))) | err_out(s,2,"-")
  649.     }
  650.     s2 ||:= tab(0)
  651.     }
  652.     return s2
  653.  
  654. end
  655.  
  656.  
  657.  
  658. procedure tab_bal(l,i1,i2)
  659.     i := 0
  660.     i1_count := 0; i2_count := 0
  661.     while (i +:= 1) <= *l do {
  662.     case l[i] of {
  663.         i1  : i1_count +:= 1
  664.         i2  : i2_count +:= 1
  665.     }
  666.     if i1_count = i2_count
  667.     then suspend i
  668.     }
  669. end
  670.  
  671.  
  672. procedure match_positive_ints(l)
  673.     
  674.     # Matches the longest sequence of positive integers in l,
  675.     # beginning at l[1], which neither contains, nor is fol-
  676.     # lowed by a negative integer.  Returns the first position
  677.     # after the match.  Hence, given [55, 55, 55, -42, 55],
  678.     # match_positive_ints will return 3.  [55, -42] will cause
  679.     # it to fail rather than return 1 (NOTE WELL!).
  680.  
  681.     every i := 1 to *l do {
  682.     if l[i] < 0
  683.     then return (3 < i) - 1 | fail
  684.     }
  685.     return *l + 1
  686.  
  687. end
  688.  
  689.  
  690. procedure Ints2String(l)
  691.     tmp := ""
  692.     every tmp ||:= char(!l)
  693.     return tmp
  694. end
  695.  
  696.  
  697. procedure StripChar(s,s2)
  698.     if find(s2,s) then {
  699.     tmp := ""
  700.     s ? {
  701.         while tmp ||:= tab(find("s2"))
  702.         do tab(many(cset(s2)))
  703.         tmp ||:= tab(0)
  704.     }
  705.     }
  706.     return \tmp | s
  707. end
  708.